﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Factories;
using FarseerPhysics.Collision;
using FarseerPhysics.Collision.Shapes;
using Somnus.Utils;
using Somnus.Core;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Common;

namespace Somnus.Objects
{
    class PhysicsObject
    {
        #region Variáveis Privadas
        
        /// <summary>
        /// Posição X do objeto
        /// </summary>
        private float _x = 0;

        /// <summary>
        /// Posição Y do objeto
        /// </summary>
        private float _y = 0;

        /// <summary>
        /// Tamanho da body do objeto
        /// </summary>
        private float _width;

        /// <summary>
        /// Tamanho da body do objeto
        /// </summary>
        private float _height;
        


        /// <summary>
        /// Nome do objeto
        /// </summary>
        private String _name;

        #endregion

        #region Variáveis Protegidas

        /// <summary>
        /// fixture da box
        /// </summary>
        protected Fixture _fixture;

        /// <summary>
        /// Shape da box
        /// </summary>
        protected PolygonShape _shape;
        
        /// <summary>
        /// Lista de objetos que estão atualmente colidindo
        /// </summary>
        protected List<Fixture> colliderList;

        /// <summary>
        /// Fator de escala, pixel x metros
        /// </summary>
        protected float _scaleFactor;

        #endregion

        #region Variáveis Publicas

        /// <summary>
        /// Essa variável representa o corpo do objeto, a box de colisão e é responsável pela existência do objeto no mundo físico
        /// </summary>
        public Body body;

        /// <summary>
        /// Essa variável representa a arte do objeto
        /// </summary>
        public AnimatedObject art;

        /// <summary>
        /// Essa variável controla se art vai ou não ser scalonada para o tamanho da box
        /// </summary>
        public Boolean scaleArt;

        /// <summary>
        /// O offsetX controla a correção de posicionamento entre a box e a arte horizontalmente
        /// </summary>
        public float offsetX;

        /// <summary>
        /// O offsetY controla a correção de posicionamento entre a box e a arte verticalmente
        /// </summary>
        public float offsetY;

        /// <summary>
        /// Variável que controla se o objeto irá morrer depois que acabar o step
        /// </summary>
        public bool kill;

        public float rounding;

        #endregion

        public PhysicsObject(DreamWorld world, String name, Texture2D texture, Nullable<Rectangle> sourceRectangle, BodyType bodyType, float x, float y, float width, float height)
        {
            this._width = width;
            this._height = height;
            this._scaleFactor = world.ScaleFactor;
            this.scaleArt = true;
            this._name = name;
            this.colliderList = new List<Fixture>();
            this.rounding = 0.05f;

            //Criação do body
            CreateBody(world);
            CreateShape(width, height);
            CreateFixture();

            body.Position = new Vector2(x / _scaleFactor, y / _scaleFactor);
            body.FixedRotation = true;
            body.Mass = 1;
            body.BeforeCollision += HandlePreCollision;
            body.OnCollision += HandleBeginContact;
            body.OnSeparation += HandleEndContact;

        
            //Criação do art
            if(texture != null)
                art = new AnimatedObject(texture, sourceRectangle, 0.5f);

            offsetX = 0;
            offsetY = 0;

            world.addObject(this);
        }

        #region Body, Shape and Fixture

        protected virtual void CreateBody(World world)
        {
            //Dê override nessa função para mudar o tipo do body.
            //Static, Dynamic, Kinemact

            body = new Body(world);
            body.BodyType = BodyType.Static;
        }

        protected virtual void CreateShape(float width, float height)
        {
            /*Dê override nessa função para mudar o shape do objeto
             * Você pode usar a função SetAsBox, ou outras funções da classe 
             * Shape e da classe PolygonTools que criam vértices.
             * 
             * Você também pode criar seus próprios vértices.
             */
            float xZ = width / _scaleFactor;
            float yZ = height / _scaleFactor;
            float xO = xZ / 2;
            float yO = yZ / 2;
            float r = rounding;

            Vertices vertices = new Vertices();
            
            vertices.Add(new Vector2(0 - xO          ,      0 + r - yO));
            vertices.Add(new Vector2(0 + r - xO      ,      0 - yO));

            vertices.Add(new Vector2(xZ - r - xO     ,      0 - yO));
            vertices.Add(new Vector2(xZ - xO         ,      0 + r - yO));

            vertices.Add(new Vector2(xZ - xO         ,      yZ - r - yO));
            vertices.Add(new Vector2(xZ - r - xO     ,      yZ - yO));

            vertices.Add(new Vector2(0 + r - xO      ,      yZ - yO));
            vertices.Add(new Vector2(0 - xO          ,      yZ - r - yO));

            _shape = new PolygonShape(1);

            _shape.Set(vertices);
        }

        protected virtual void CreateFixture()
        {
            _fixture = new Fixture(body, _shape);
            _fixture.Restitution = 0f;
            _fixture.Friction = 0.6f;
            _fixture.UserData = this;
        }

        #endregion

        #region Collisions


        protected virtual bool HandlePreCollision(Fixture thisFixture, Fixture otherFixture)
        {
            return true;
        }

        protected virtual bool HandleBeginContact(Fixture thisFixture, Fixture otherFixture, Contact contact)
        {
            colliderList.Add(otherFixture);
            return true;
        }

        protected virtual void HandleEndContact(Fixture thisFixture, Fixture otherFixture)
        {
            colliderList.Remove(otherFixture);
        }

        protected virtual void HandleIsColliding(Fixture thisFixture, Fixture otherFixture)
        {
            
        }

        #endregion

        public virtual void update(GameTime gameTime)
		{
            foreach (Fixture f in colliderList)
            {
                HandleIsColliding(_fixture, f);
            }

            

            if (art != null)
            {
                art.update(gameTime);
                art.x = (body.Position.X * _scaleFactor) + offsetX;
                art.y = (body.Position.Y * _scaleFactor) + offsetY;
                art.rotation = Rotation;

                if (scaleArt)
                {
                    art.Width = _width;
                    art.Height = _height;
                }
            }
		}

        public void draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            if (art != null)
            {
                art.draw(gameTime, spriteBatch);
            }
        }

        public void destroy()
        {
            this.body.Dispose();
        }


        #region GETS & SETS

        public String Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
            }
        }

        public float x
        {
            get
            {
                if (body != null)
                    return body.Position.X * _scaleFactor;
                else
                    return _x * _scaleFactor;
            }

            set
            {
                _x = value / _scaleFactor;

                if (body != null)
                {
                    Vector2 pos = body.Position;
                    pos.X = _x;
                    body.SetTransform(pos, body.Rotation);
                }
            }
        }

        public float y
        {
            get
            {
                if (body != null)
                    return body.Position.Y * _scaleFactor;
                else
                    return _y * _scaleFactor;
            }

            set
            {
                _y = value / _scaleFactor;

                if (body != null)
                {
                    Vector2 pos = body.Position;
                    pos.Y = _y;
                    body.SetTransform(pos, body.Rotation);
                }
            }
        }

        public float Rotation
        {
            get
            {
                
                return (float)MathHelper.ToDegrees(body.Rotation);
            }

            set
            {
                body.SetTransform(body.Position, MathHelper.ToRadians(value));
            }
        }

        #endregion




    }
}
